using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NotifyCenter.MediatR.Etos;

namespace NotifyCenter.MediatR.Webhooks;

public class NotificationWebhookDispatcher(
    INotificationWebhookQueue queue,
    IHttpClientFactory httpClientFactory,
    ILogger<NotificationWebhookDispatcher> logger) : BackgroundService
{
    private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Notification webhook dispatcher started.");

        while (!stoppingToken.IsCancellationRequested)
        {
            NotificationWebhookEto? notification = null;

            try
            {
                notification = await queue.DequeueAsync(stoppingToken);
                await SendAsync(notification, stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                // graceful shutdown
            }
            catch (Exception ex)
            {
                if (notification is null)
                {
                    logger.LogError(ex, "Webhook dispatcher encountered an error while waiting for work.");
                }
                else
                {
                    logger.LogError(ex, "Webhook dispatch failed for record {RecordId}", notification.RecordId);
                }
            }
        }

        logger.LogInformation("Notification webhook dispatcher stopped.");
    }

    private async Task SendAsync(NotificationWebhookEto eto, CancellationToken cancellationToken)
    {
        var payload = new
        {
            eto.RecordId,
            eto.ProjectId,
            eto.NotificationRuleId,
            eto.NotificationName,
            NotificationType = eto.NotificationType.ToString(),
            NotificationTypeValue = eto.NotificationType,
            Status = eto.Status.ToString(),
            StatusValue = (int)eto.Status,
            eto.StatusMessage,
            eto.OccurredAtUtc
        };

        var payloadJson = JsonSerializer.Serialize(payload, SerializerOptions);

        using var request = new HttpRequestMessage(HttpMethod.Post, eto.Callback)
        {
            Content = new StringContent(payloadJson, Encoding.UTF8, "application/json")
        };

        if (!string.IsNullOrWhiteSpace(eto.Secret))
        {
            var signature = ComputeSignature(eto.Secret!, payloadJson);
            request.Headers.TryAddWithoutValidation("X-NotifyCenter-Signature", signature);
        }

        var client = httpClientFactory.CreateClient(nameof(NotificationWebhookDispatcher));
        client.Timeout = TimeSpan.FromSeconds(5);

        using var response = await client.SendAsync(request, cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync(cancellationToken);
            logger.LogWarning(
                "Webhook {Url} responded with {StatusCode} for record {RecordId}. Body: {Body}",
                eto.Callback,
                response.StatusCode,
                eto.RecordId,
                body);
        }
    }

    private static string ComputeSignature(string secret, string payload)
    {
        var secretBytes = Encoding.UTF8.GetBytes(secret);
        var payloadBytes = Encoding.UTF8.GetBytes(payload);
        using var hmac = new HMACSHA256(secretBytes);
        return Convert.ToHexString(hmac.ComputeHash(payloadBytes)).ToLowerInvariant();
    }
}
